/*
 * Copyright (C) 2005-2011 MaNGOS <http://www.getmangos.com/>
 *
 * Copyright (C) 2008-2011 Trinity <http://www.trinitycore.org/>
 *
 * Copyright (C) 2010-2011 ProjectSkyfire <http://www.projectskyfire.org/>
 * 
 * Copyright (C) 2011 ArkCORE <http://www.arkania.net/>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 */

#include "gamePCH.h"
#include "Common.h"
#include "ObjectMgr.h"
#include "Player.h"
#include "World.h"
#include "WorldPacket.h"
#include "WorldSession.h"
#include "DatabaseEnv.h"
#include "SQLStorage.h"
#include "DBCStores.h"
#include "ScriptMgr.h"
#include "AccountMgr.h"
#include "AuctionHouseMgr.h"
#include "Item.h"
#include "Language.h"
#include "Logging/Log.h"
#include <vector>

enum eAuctionHouse {
	AH_MINIMUM_DEPOSIT = 100,
};

AuctionHouseMgr::AuctionHouseMgr() {
}

AuctionHouseMgr::~AuctionHouseMgr() {
	for (ItemMap::iterator itr = mAitems.begin(); itr != mAitems.end(); ++itr)
		delete itr->second;
}

AuctionHouseObject * AuctionHouseMgr::GetAuctionsMap(uint32 factionTemplateId) {
	if (sWorld->getBoolConfig(CONFIG_ALLOW_TWO_SIDE_INTERACTION_AUCTION))
		return &mNeutralAuctions;

	// team have linked auction houses
	FactionTemplateEntry const* u_entry = sFactionTemplateStore.LookupEntry(
			factionTemplateId);
	if (!u_entry)
		return &mNeutralAuctions;
	else if (u_entry->ourMask & FACTION_MASK_ALLIANCE)
		return &mAllianceAuctions;
	else if (u_entry->ourMask & FACTION_MASK_HORDE)
		return &mHordeAuctions;
	else
		return &mNeutralAuctions;
}

uint32 AuctionHouseMgr::GetAuctionDeposit(AuctionHouseEntry const* entry,
		uint32 time, Item *pItem, uint32 count) {
	uint32 MSV = pItem->GetProto()->SellPrice;

	if (MSV <= 0)
		return AH_MINIMUM_DEPOSIT;

	float multiplier = CalculatePctN(float(entry->depositPercent), 3);
	uint32 timeHr = (((time / 60) / 60) / 12);
	uint32 deposit = uint32(multiplier * MSV * count / 3) * timeHr * 3;

	sLog->outDebug(LOG_FILTER_AUCTIONHOUSE, "MSV:        %u", MSV);
	sLog->outDebug(LOG_FILTER_AUCTIONHOUSE, "Items:      %u", count);
	sLog->outDebug(LOG_FILTER_AUCTIONHOUSE, "Multiplier: %f", multiplier);
	sLog->outDebug(LOG_FILTER_AUCTIONHOUSE, "Deposit:    %u", deposit);

	if (deposit < AH_MINIMUM_DEPOSIT)
		return AH_MINIMUM_DEPOSIT;
	else
		return deposit;
}

//does not clear ram
void AuctionHouseMgr::SendAuctionWonMail(AuctionEntry *auction,
		SQLTransaction& trans) {
	Item *pItem = GetAItem(auction->item_guidlow);
	if (!pItem)
		return;

	uint32 bidder_accId = 0;
	uint64 bidder_guid = MAKE_NEW_GUID(auction->bidder, 0, HIGHGUID_PLAYER);
	Player *bidder = sObjectMgr->GetPlayer(bidder_guid);
	// data for gm.log
	if (sWorld->getBoolConfig(CONFIG_GM_LOG_TRADE)) {
		uint32 bidder_security = 0;
		std::string bidder_name;
		if (bidder) {
			bidder_accId = bidder->GetSession()->GetAccountId();
			bidder_security = bidder->GetSession()->GetSecurity();
			bidder_name = bidder->GetName();
		} else {
			bidder_accId = sObjectMgr->GetPlayerAccountIdByGUID(bidder_guid);
			bidder_security = sAccountMgr->GetSecurity(bidder_accId, realmID);

			if (bidder_security > SEC_PLAYER) // not do redundant DB requests
					{
				if (!sObjectMgr->GetPlayerNameByGUID(bidder_guid, bidder_name))
					bidder_name = sObjectMgr->GetArkCoreStringForDBCLocale(
							LANG_UNKNOWN);
			}
		}
		if (bidder_security > SEC_PLAYER) {
			std::string owner_name;
			if (!sObjectMgr->GetPlayerNameByGUID(auction->owner, owner_name))
				owner_name = sObjectMgr->GetArkCoreStringForDBCLocale(
						LANG_UNKNOWN);

			uint32 owner_accid = sObjectMgr->GetPlayerAccountIdByGUID(
					auction->owner);

			sLog->outCommand(
					bidder_accId,
					"GM %s (Account: %u) won item in auction: %s (Entry: %u Count: %u) and pay money: %u. Original owner %s (Account: %u)",
					bidder_name.c_str(), bidder_accId, pItem->GetProto()->Name1,
					pItem->GetEntry(), pItem->GetCount(), auction->bid,
					owner_name.c_str(), owner_accid);
		}
	}

	// receiver exist
	if (bidder || bidder_accId) {
		std::ostringstream msgAuctionWonSubject;
		msgAuctionWonSubject << auction->item_template << ":0:" << AUCTION_WON;

		std::ostringstream msgAuctionWonBody;
		msgAuctionWonBody.width(16);
		msgAuctionWonBody << std::right << std::hex << auction->owner;
		msgAuctionWonBody << std::dec << ":" << auction->bid << ":"
				<< auction->buyout;
		sLog->outDebug(LOG_FILTER_AUCTIONHOUSE, "AuctionWon body string : %s",
				msgAuctionWonBody.str().c_str());

		// set owner to bidder (to prevent delete item with sender char deleting)
		// owner in `data` will set at mail receive and item extracting
		PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(
				CHAR_SET_ITEM_OWNER);
		stmt->setUInt32(0, auction->bidder);
		stmt->setUInt32(1, pItem->GetGUIDLow());
		trans->Append(stmt);

		if (bidder) {
			bidder->GetSession()->SendAuctionBidderNotification(
					auction->GetHouseId(), auction->Id, bidder_guid, 0, 0,
					auction->item_template);
			// FIXME: for offline player need also
			bidder->GetAchievementMgr().UpdateAchievementCriteria(
					ACHIEVEMENT_CRITERIA_TYPE_WON_AUCTIONS, 1);
		}

		MailDraft(msgAuctionWonSubject.str(), msgAuctionWonBody.str()).AddItem(
				pItem).SendMailTo(trans, MailReceiver(bidder, auction->bidder),
				auction, MAIL_CHECK_MASK_COPIED);
	}
}

void AuctionHouseMgr::SendAuctionSalePendingMail(AuctionEntry * auction,
		SQLTransaction& trans) {
	uint64 owner_guid = MAKE_NEW_GUID(auction->owner, 0, HIGHGUID_PLAYER);
	Player *owner = sObjectMgr->GetPlayer(owner_guid);
	uint32 owner_accId = sObjectMgr->GetPlayerAccountIdByGUID(owner_guid);
	// owner exist (online or offline)
	if (owner || owner_accId) {
		std::ostringstream msgAuctionSalePendingSubject;
		msgAuctionSalePendingSubject << auction->item_template << ":0:"
				<< AUCTION_SALE_PENDING;

		std::ostringstream msgAuctionSalePendingBody;
		uint32 auctionCut = auction->GetAuctionCut();

		time_t distrTime = time(NULL)
				+ sWorld->getIntConfig(CONFIG_MAIL_DELIVERY_DELAY);

		msgAuctionSalePendingBody.width(16);
		msgAuctionSalePendingBody << std::right << std::hex << auction->bidder;
		msgAuctionSalePendingBody << std::dec << ":" << auction->bid << ":"
				<< auction->buyout;
		msgAuctionSalePendingBody << ":" << auction->deposit << ":"
				<< auctionCut << ":0:";
		msgAuctionSalePendingBody << secsToTimeBitFields(distrTime);

		sLog->outDebug(LOG_FILTER_AUCTIONHOUSE,
				"AuctionSalePending body string : %s",
				msgAuctionSalePendingBody.str().c_str());

		MailDraft(msgAuctionSalePendingSubject.str(),
				msgAuctionSalePendingBody.str()).SendMailTo(trans,
				MailReceiver(owner, auction->owner), auction,
				MAIL_CHECK_MASK_COPIED);
	}
}

//call this method to send mail to auction owner, when auction is successful, it does not clear ram
void AuctionHouseMgr::SendAuctionSuccessfulMail(AuctionEntry * auction,
		SQLTransaction& trans) {
	uint64 owner_guid = MAKE_NEW_GUID(auction->owner, 0, HIGHGUID_PLAYER);
	Player *owner = sObjectMgr->GetPlayer(owner_guid);
	uint32 owner_accId = sObjectMgr->GetPlayerAccountIdByGUID(owner_guid);
	// owner exist
	if (owner || owner_accId) {
		std::ostringstream msgAuctionSuccessfulSubject;
		msgAuctionSuccessfulSubject << auction->item_template << ":0:"
				<< AUCTION_SUCCESSFUL;

		std::ostringstream auctionSuccessfulBody;
		uint32 auctionCut = auction->GetAuctionCut();

		auctionSuccessfulBody.width(16);
		auctionSuccessfulBody << std::right << std::hex << auction->bidder;
		auctionSuccessfulBody << std::dec << ":" << auction->bid << ":"
				<< auction->buyout;
		auctionSuccessfulBody << ":" << auction->deposit << ":" << auctionCut;

		sLog->outDebug(LOG_FILTER_AUCTIONHOUSE,
				"AuctionSuccessful body string : %s",
				auctionSuccessfulBody.str().c_str());

		uint64 profit = auction->bid + auction->deposit - auctionCut;

		//FIXME: what do if owner offline
		if (owner) {
			owner->GetAchievementMgr().UpdateAchievementCriteria(
					ACHIEVEMENT_CRITERIA_TYPE_GOLD_EARNED_BY_AUCTIONS, profit);
			owner->GetAchievementMgr().UpdateAchievementCriteria(
					ACHIEVEMENT_CRITERIA_TYPE_HIGHEST_AUCTION_SOLD,
					auction->bid);
			//send auction owner notification, bidder must be current!
			owner->GetSession()->SendAuctionOwnerNotification(auction);
		}
		MailDraft(msgAuctionSuccessfulSubject.str(),
				auctionSuccessfulBody.str()).AddMoney(profit).SendMailTo(trans,
				MailReceiver(owner, auction->owner), auction,
				MAIL_CHECK_MASK_COPIED,
				sWorld->getIntConfig(CONFIG_MAIL_DELIVERY_DELAY));
	}
}

//does not clear ram
void AuctionHouseMgr::SendAuctionExpiredMail(AuctionEntry * auction,
		SQLTransaction& trans) {
	//return an item in auction to its owner by mail
	Item *pItem = GetAItem(auction->item_guidlow);
	if (!pItem)
		return;

	uint64 owner_guid = MAKE_NEW_GUID(auction->owner, 0, HIGHGUID_PLAYER);
	Player *owner = sObjectMgr->GetPlayer(owner_guid);
	uint32 owner_accId = sObjectMgr->GetPlayerAccountIdByGUID(owner_guid);
	// owner exist
	if (owner || owner_accId) {
		std::ostringstream subject;
		subject << auction->item_template << ":0:" << AUCTION_EXPIRED << ":0:0";

		if (owner)
			owner->GetSession()->SendAuctionOwnerNotification(auction);

		MailDraft(subject.str(), "") // TODO: fix body
		.AddItem(pItem).SendMailTo(trans, MailReceiver(owner, auction->owner),
				auction, MAIL_CHECK_MASK_COPIED, 0);
	}
}

void AuctionHouseMgr::SendAuctionRemovedMail(AuctionEntry * auction,
		SQLTransaction& trans) {
	uint64 owner_guid = MAKE_NEW_GUID(auction->owner, 0, HIGHGUID_PLAYER);
	Player *owner = sObjectMgr->GetPlayer(owner_guid);

	if (owner) {
		std::ostringstream subject;
		subject << auction->item_template << ":0:" << AUCTION_CANCELED
				<< ":0:0";

		if (owner)
			owner->GetSession()->SendAuctionRemovedNotification(auction);

		MailDraft(subject.str(), "").SendMailTo(trans,
				MailReceiver(owner, auction->owner), auction,
				MAIL_CHECK_MASK_COPIED, 0);
	}
}

//this function sends mail to old bidder
void AuctionHouseMgr::SendAuctionOutbiddedMail(AuctionEntry *auction,
		uint64 newPrice, Player* newBidder, SQLTransaction& trans) {
	uint64 oldBidder_guid = MAKE_NEW_GUID(auction->bidder, 0, HIGHGUID_PLAYER);
	Player *oldBidder = sObjectMgr->GetPlayer(oldBidder_guid);

	uint32 oldBidder_accId = 0;
	if (!oldBidder)
		oldBidder_accId = sObjectMgr->GetPlayerAccountIdByGUID(oldBidder_guid);

	// old bidder exist
	if (oldBidder || oldBidder_accId) {
		std::ostringstream msgAuctionOutbiddedSubject;
		msgAuctionOutbiddedSubject << auction->item_template << ":0:"
				<< AUCTION_OUTBIDDED << ":0:0";

		if (oldBidder && newBidder)
			oldBidder->GetSession()->SendAuctionBidderNotification(
					auction->GetHouseId(), auction->Id, newBidder->GetGUID(),
					newPrice, auction->GetAuctionOutBid(),
					auction->item_template);

		MailDraft(msgAuctionOutbiddedSubject.str(), "") // TODO: fix body
		.AddMoney(auction->bid).SendMailTo(trans,
				MailReceiver(oldBidder, auction->bidder), auction,
				MAIL_CHECK_MASK_COPIED);
	}
}

//this function sends mail, when auction is cancelled to old bidder
void AuctionHouseMgr::SendAuctionCancelledToBidderMail(AuctionEntry* auction,
		SQLTransaction& trans) {
	uint64 bidder_guid = MAKE_NEW_GUID(auction->bidder, 0, HIGHGUID_PLAYER);
	Player *bidder = sObjectMgr->GetPlayer(bidder_guid);

	uint32 bidder_accId = 0;
	if (!bidder)
		bidder_accId = sObjectMgr->GetPlayerAccountIdByGUID(bidder_guid);

	// bidder exist
	if (bidder || bidder_accId) {
		std::ostringstream msgAuctionCancelledSubject;
		msgAuctionCancelledSubject << auction->item_template << ":0:"
				<< AUCTION_CANCELLED_TO_BIDDER << ":0:0";

		MailDraft(msgAuctionCancelledSubject.str(), "") // TODO: fix body
		.AddMoney(auction->bid).SendMailTo(trans,
				MailReceiver(bidder, auction->bidder), auction,
				MAIL_CHECK_MASK_COPIED);
	}
}

void AuctionHouseMgr::LoadAuctionItems() {
	uint32 oldMSTime = getMSTime();

	// data needs to be at first place for Item::LoadFromDB
	PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(
			CHAR_LOAD_AUCTION_ITEMS);
	PreparedQueryResult result = CharacterDatabase.Query(stmt);

	if (!result) {
		sLog->outString(
				">> Loaded 0 auction items. DB table `auctionhouse` or `item_instance` is empty!");
		sLog->outString();
		return;
	}

	uint32 count = 0;

	do {
		Field* fields = result->Fetch();

		uint32 item_guid = fields[11].GetUInt32();
		uint32 item_template = fields[12].GetUInt32();

		ItemPrototype const *proto = ObjectMgr::GetItemPrototype(item_template);
		if (!proto) {
			sLog->outError(
					"AuctionHouseMgr::LoadAuctionItems: Unknown item (GUID: %u id: #%u) in auction, skipped.",
					item_guid, item_template);
			continue;
		}

		Item *item = NewItemOrBag(proto);
		if (!item->LoadFromDB(item_guid, 0, fields, item_template)) {
			delete item;
			continue;
		}
		AddAItem(item);

		++count;
	} while (result->NextRow());

	sLog->outString(">> Loaded %u auction items in %u ms", count,
			GetMSTimeDiffToNow(oldMSTime));
	sLog->outString();
}

void AuctionHouseMgr::LoadAuctions() {
	uint32 oldMSTime = getMSTime();

	PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(
			CHAR_LOAD_AUCTIONS);
	PreparedQueryResult result = CharacterDatabase.Query(stmt);

	if (!result) {
		sLog->outString(
				">> Loaded 0 auctions. DB table `auctionhouse` is empty.");
		sLog->outString();
		return;
	}

	uint32 count = 0;

	SQLTransaction trans = CharacterDatabase.BeginTransaction();
	do {
		Field* fields = result->Fetch();

		AuctionEntry *aItem = new AuctionEntry();
		if (!aItem->LoadFromDB(fields)) {
			aItem->DeleteFromDB(trans);
			delete aItem;
			continue;
		}

		GetAuctionsMap(aItem->factionTemplateId)->AddAuction(aItem);
		count++;
	} while (result->NextRow());

	CharacterDatabase.CommitTransaction(trans);

	sLog->outString(">> Loaded %u auctions in %u ms", count,
			GetMSTimeDiffToNow(oldMSTime));
	sLog->outString();
}

void AuctionHouseMgr::AddAItem(Item* it) {
	ASSERT(it);
	ASSERT(mAitems.find(it->GetGUIDLow()) == mAitems.end());
	mAitems[it->GetGUIDLow()] = it;
}

bool AuctionHouseMgr::RemoveAItem(uint32 id) {
	ItemMap::iterator i = mAitems.find(id);
	if (i == mAitems.end())
		return false;

	mAitems.erase(i);
	return true;
}

void AuctionHouseMgr::Update() {
	mHordeAuctions.Update();
	mAllianceAuctions.Update();
	mNeutralAuctions.Update();
}

AuctionHouseEntry const* AuctionHouseMgr::GetAuctionHouseEntry(
		uint32 factionTemplateId) {
	uint32 houseid = 7; // goblin auction house

	if (!sWorld->getBoolConfig(CONFIG_ALLOW_TWO_SIDE_INTERACTION_AUCTION)) {
		//FIXME: found way for proper auctionhouse selection by another way
		// AuctionHouse.dbc have faction field with _player_ factions associated with auction house races.
		// but no easy way convert creature faction to player race faction for specific city
		switch (factionTemplateId) {
		case 12:
			houseid = 1;
			break; // human
		case 29:
			houseid = 6;
			break; // orc, and generic for horde
		case 55:
			houseid = 2;
			break; // dwarf, and generic for alliance
		case 68:
			houseid = 4;
			break; // undead
		case 80:
			houseid = 3;
			break; // n-elf
		case 104:
			houseid = 5;
			break; // trolls
		case 120:
			houseid = 7;
			break; // booty bay, neutral
		case 474:
			houseid = 7;
			break; // gadgetzan, neutral
		case 855:
			houseid = 7;
			break; // everlook, neutral
		case 1604:
			houseid = 6;
			break; // b-elfs,
		default: // for unknown case
		{
			FactionTemplateEntry const* u_entry =
					sFactionTemplateStore.LookupEntry(factionTemplateId);
			if (!u_entry)
				houseid = 7; // goblin auction house
			else if (u_entry->ourMask & FACTION_MASK_ALLIANCE)
				houseid = 1; // human auction house
			else if (u_entry->ourMask & FACTION_MASK_HORDE)
				houseid = 6; // orc auction house
			else
				houseid = 7; // goblin auction house
			break;
		}
		}
	}

	return sAuctionHouseStore.LookupEntry(houseid);
}

void AuctionHouseObject::AddAuction(AuctionEntry *auction) {
	ASSERT(auction);

	AuctionsMap[auction->Id] = auction;
	sScriptMgr->OnAuctionAdd(this, auction);
}

bool AuctionHouseObject::RemoveAuction(AuctionEntry *auction,
		uint32 /*item_template*/) {
	bool wasInMap = AuctionsMap.erase(auction->Id) ? true : false;

	sScriptMgr->OnAuctionRemove(this, auction);

	// we need to delete the entry, it is not referenced any more
	delete auction;
	return wasInMap;
}

void AuctionHouseObject::Update() {
	time_t curTime = sWorld->GetGameTime();
	///- Handle expired auctions

	// If storage is empty, no need to update. next == NULL in this case.
	if (AuctionsMap.empty())
		return;

	QueryResult result = CharacterDatabase.PQuery(
			"SELECT id FROM auctionhouse WHERE time <= %u ORDER BY TIME ASC",
			(uint32) curTime + 60);

	if (!result)
		return;

	do {
		// from auctionhousehandler.cpp, creates auction pointer & player pointer
		AuctionEntry* auction = GetAuction(result->Fetch()->GetUInt32());

		if (!auction)
			continue;

		SQLTransaction trans = CharacterDatabase.BeginTransaction();

		///- Either cancel the auction if there was no bidder
		if (auction->bidder == 0) {
			sAuctionMgr->SendAuctionExpiredMail(auction, trans);
			sScriptMgr->OnAuctionExpire(this, auction);
		}
		///- Or perform the transaction
		else {
			//we should send an "item sold" message if the seller is online
			//we send the item to the winner
			//we send the money to the seller
			sAuctionMgr->SendAuctionSuccessfulMail(auction, trans);
			sAuctionMgr->SendAuctionWonMail(auction, trans);
			sScriptMgr->OnAuctionSuccessful(this, auction);
		}

		uint32 item_template = auction->item_template;

		///- In any case clear the auction
		auction->DeleteFromDB(trans);
		CharacterDatabase.CommitTransaction(trans);

		RemoveAuction(auction, item_template);
		sAuctionMgr->RemoveAItem(auction->item_guidlow);
	} while (result->NextRow());
}

void AuctionHouseObject::BuildListBidderItems(WorldPacket& data, Player* player,
		uint32& count, uint32& totalcount) {
	for (AuctionEntryMap::const_iterator itr = AuctionsMap.begin();
			itr != AuctionsMap.end(); ++itr) {
		AuctionEntry *Aentry = itr->second;
		if (Aentry && Aentry->bidder == player->GetGUIDLow()) {
			if (itr->second->BuildAuctionInfo(data))
				++count;

			++totalcount;
		}
	}
}

void AuctionHouseObject::BuildListOwnerItems(WorldPacket& data, Player* player,
		uint32& count, uint32& totalcount) {
	for (AuctionEntryMap::const_iterator itr = AuctionsMap.begin();
			itr != AuctionsMap.end(); ++itr) {
		AuctionEntry *Aentry = itr->second;
		if (Aentry && Aentry->owner == player->GetGUIDLow()) {
			if (Aentry->BuildAuctionInfo(data))
				++count;

			++totalcount;
		}
	}
}

void AuctionHouseObject::BuildListAuctionItems(WorldPacket& data,
		Player* player, std::wstring const& wsearchedname, uint32 listfrom,
		uint8 levelmin, uint8 levelmax, uint8 usable, uint32 inventoryType,
		uint32 itemClass, uint32 itemSubClass, uint32 quality, uint32& count,
		uint32& totalcount) {
	int loc_idx = player->GetSession()->GetSessionDbLocaleIndex();
	int locdbc_idx = player->GetSession()->GetSessionDbcLocale();

	for (AuctionEntryMap::const_iterator itr = AuctionsMap.begin();
			itr != AuctionsMap.end(); ++itr) {
		AuctionEntry *Aentry = itr->second;
		Item *item = sAuctionMgr->GetAItem(Aentry->item_guidlow);
		if (!item)
			continue;

		ItemPrototype const *proto = item->GetProto();

		if (itemClass != 0xffffffff && proto->Class != itemClass)
			continue;

		if (itemSubClass != 0xffffffff && proto->SubClass != itemSubClass)
			continue;

		if (inventoryType != 0xffffffff
				&& proto->InventoryType != inventoryType)
			continue;

		if (quality != 0xffffffff && proto->Quality != quality)
			continue;

		if (levelmin != 0x00
				&& (proto->RequiredLevel < levelmin
						|| (levelmax != 0x00 && proto->RequiredLevel > levelmax)))
			continue;

		if (usable != 0x00 && player->CanUseItem(item) != EQUIP_ERR_OK)
			continue;

		// Allow search by suffix (ie: of the Monkey) or partial name (ie: Monkey)
		// No need to do any of this if no search term was entered
		if (!wsearchedname.empty()) {
			std::string name = proto->Name1;
			if (name.empty())
				continue;

			// local name
			if (loc_idx >= 0)
				if (ItemLocale const *il = sObjectMgr->GetItemLocale(proto->ItemId))
					sObjectMgr->GetLocaleString(il->Name, loc_idx, name);

			// DO NOT use GetItemEnchantMod(proto->RandomProperty) as it may return a result
			//  that matches the search but it may not equal item->GetItemRandomPropertyId()
			//  used in BuildAuctionInfo() which then causes wrong items to be listed
			int32 propRefID = item->GetItemRandomPropertyId();

			if (propRefID) {
				// Append the suffix to the name (ie: of the Monkey) if one exists
				// These are found in ItemRandomProperties.dbc, not ItemRandomSuffix.dbc
				//  even though the DBC names seem misleading
				const ItemRandomPropertiesEntry *itemRandProp =
						sItemRandomPropertiesStore.LookupEntry(propRefID);

				if (itemRandProp) {
					DBCString temp = itemRandProp->nameSuffix;
					//char* temp = itemRandProp->nameSuffix;

					// dbc local name
					if (temp) {
						if (locdbc_idx >= 0) {
							// Append the suffix (ie: of the Monkey) to the name using localization
							name += " ";
							name += temp;
						} else {
							// Invalid localization? Append the suffix using default enUS
							name += " ";
							name += temp;
						}
					}
				}
			}

			// Perform the search (with or without suffix)
			if (!Utf8FitTo(name, wsearchedname))
				continue;
		}

		// Add the item if no search term or if entered search term was found
		if (count < 50 && totalcount >= listfrom) {
			++count;
			Aentry->BuildAuctionInfo(data);
		}
		++totalcount;
	}
}

//this function inserts to WorldPacket auction's data
bool AuctionEntry::BuildAuctionInfo(WorldPacket & data) const {
	Item *pItem = sAuctionMgr->GetAItem(item_guidlow);
	if (!pItem) {
		sLog->outError("auction to item, that doesn't exist !!!!");
		return false;
	}
	data << uint32(Id);
	data << uint32(pItem->GetEntry());

	for (uint8 i = 0; i < MAX_INSPECTED_ENCHANTMENT_SLOT; ++i) {
		data << uint32(pItem->GetEnchantmentId(EnchantmentSlot(i)));
		data << uint32(pItem->GetEnchantmentDuration(EnchantmentSlot(i)));
		data << uint32(pItem->GetEnchantmentCharges(EnchantmentSlot(i)));
	}

	for (uint8 i = 0; i < 2; ++i) {
		data << uint32(0);
		data << uint32(0);
		data << uint32(0);
	}

	data << int32(pItem->GetItemRandomPropertyId()); //random item property id
	data << uint32(pItem->GetItemSuffixFactor()); //SuffixFactor
	data << uint32(pItem->GetCount()); //item->count
	data << uint32(pItem->GetSpellCharges()); //item->charge FFFFFFF
	data << uint32(0); //Unknown
	data << uint64(owner); //Auction->owner
	data << uint64(startbid); //Auction->startbid (not sure if useful)
	data << uint64(bid ? GetAuctionOutBid() : 0);
	//minimal outbid
	data << uint64(buyout); //auction->buyout
	data << uint32((expire_time - time(NULL)) * IN_MILLISECONDS); //time left
	data << uint64(bidder); //auction->bidder current
	data << uint64(bid); //current bid
	return true;
}

uint32 AuctionEntry::GetAuctionCut() const {
	int32 cut = int32(CalculatePctU(sWorld->getRate(RATE_AUCTION_CUT),auctionHouseEntry->cutPercent)) * bid;
	return std::max(cut, 0);
}

/// the sum of outbid is (1% from current bid)*5, if bid is very small, it is 1c
uint64 AuctionEntry::GetAuctionOutBid() const {
	uint64 outbid = CalculatePctN(bid, 5);
	return outbid ? outbid : 1;
}

void AuctionEntry::DeleteFromDB(SQLTransaction& trans) const {
	PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(
			CHAR_DEL_AUCTION);
	stmt->setUInt32(0, Id);
	trans->Append(stmt);
}

void AuctionEntry::SaveToDB(SQLTransaction& trans) const {
	PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(
			CHAR_ADD_AUCTION);
	stmt->setUInt32(0, Id);
	stmt->setUInt32(1, auctioneer);
	stmt->setUInt32(2, item_guidlow);
	stmt->setUInt32(3, owner);
	stmt->setInt32(4, int32(buyout));
	stmt->setUInt64(5, uint64(expire_time));
	stmt->setUInt32(6, bidder);
	stmt->setInt32(7, int32(bid));
	stmt->setInt32(8, int32(startbid));
	stmt->setInt32(9, int32(deposit));
	trans->Append(stmt);
}

bool AuctionEntry::LoadFromDB(Field* fields) {
	Id = fields[0].GetUInt32();
	auctioneer = fields[1].GetUInt32();
	item_guidlow = fields[2].GetUInt32();
	item_template = fields[3].GetUInt32();
	owner = fields[4].GetUInt32();
	buyout = fields[5].GetUInt32();
	expire_time = fields[6].GetUInt32();
	bidder = fields[7].GetUInt32();
	bid = fields[8].GetUInt32();
	startbid = fields[9].GetUInt32();
	deposit = fields[10].GetUInt32();

	CreatureData const* auctioneerData = sObjectMgr->GetCreatureData(
			auctioneer);
	if (!auctioneerData) {
		sLog->outError("Auction %u has not a existing auctioneer (GUID : %u)",
				Id, auctioneer);
		return false;
	}

	CreatureInfo const* auctioneerInfo = ObjectMgr::GetCreatureTemplate(
			auctioneerData->id);
	if (!auctioneerInfo) {
		sLog->outError(
				"Auction %u has not a existing auctioneer (GUID : %u Entry: %u)",
				Id, auctioneer, auctioneerData->id);
		return false;
	}

	factionTemplateId = auctioneerInfo->faction_A;
	auctionHouseEntry = AuctionHouseMgr::GetAuctionHouseEntry(
			factionTemplateId);
	if (!auctionHouseEntry) {
		sLog->outError(
				"Auction %u has auctioneer (GUID : %u Entry: %u) with wrong faction %u",
				Id, auctioneer, auctioneerData->id, factionTemplateId);
		return false;
	}

	// check if sold item exists for guid
	// and item_template in fact (GetAItem will fail if problematic in result check in AuctionHouseMgr::LoadAuctionItems)
	if (!sAuctionMgr->GetAItem(item_guidlow)) {
		sLog->outError("Auction %u has not a existing item : %u", Id,
				item_guidlow);
		return false;
	}
	return true;
}
